package my.servlet.connector;

import my.servlet.core.container.*;
import my.servlet.core.lifecycle.LifecycleException;
import my.servlet.net.DefaultServerSocketFactory;
import my.servlet.net.ServerSocketFactory;

import java.io.IOException;
import java.net.BindException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;

public class HttpConnector implements Connector, Runnable {

    private int acceptCount = 10;
    private String address = null;
    private final int port = 8080;
    private ServerSocket serverSocket = null;
    private boolean initialized = false;
    private boolean started = false;
    private boolean stopped = false;
    private Thread thread = null;
    private String threadName = null;
    private String scheme = "http";
    private ServerSocketFactory factory = null;

    private boolean tcpNoDelay = true;
    private int connectionTimeout = -1;

    protected int minProcessors = 5;
    private final int maxProcessors = 20;
    private int curProcessors = 0;
    private final Queue<HttpProcessor> processors = new ArrayBlockingQueue<>(20);
    private final List<HttpProcessor> created = new ArrayList<>(20);

    private final Object threadSync = new Object();

    private Container container;

    @Override
    public void initialize() throws LifecycleException {
        if (initialized) {
            throw new LifecycleException("httpConnector.alreadyInitialized");
        }

        this.initialized=true;
        Exception eRethrow = null;

        // Establish a server socket on the specified port
        try {
            serverSocket = open();
        } catch (IOException | KeyManagementException | UnrecoverableKeyException
                | CertificateException | NoSuchAlgorithmException | KeyStoreException ioe) {
            eRethrow = ioe;
        }

        if (eRethrow != null) {
            throw new LifecycleException(threadName + ".open", eRethrow);
        }
    }

    private ServerSocket open()
            throws IOException, KeyStoreException, NoSuchAlgorithmException,
            CertificateException, UnrecoverableKeyException,
            KeyManagementException
    {

        // Acquire the server socket factory for this Connector
        ServerSocketFactory factory = getFactory();

        // If no address is specified, open a connection on all addresses
        if (address == null) {
            try {
                return (factory.createSocket(port, acceptCount));
            } catch (BindException be) {
                throw new BindException(be.getMessage() + ":" + port);
            }
        }

        // Open a server socket on the specified address
        try {
            InetAddress is = InetAddress.getByName(address);
            try {
                return (factory.createSocket(port, acceptCount, is));
            } catch (BindException be) {
                throw new BindException(be.getMessage() + ":" + address + ":" + port);
            }
        } catch (Exception e) {
            try {
                return (factory.createSocket(port, acceptCount));
            } catch (BindException be) {
                throw new BindException(be.getMessage() + ":" + port);
            }
        }

    }

    private HttpProcessor createProcessor() {
        synchronized (processors) {
            HttpProcessor processor = processors.poll();
            if (processor != null) {
                return processor;
            }
            if (curProcessors < maxProcessors) {
                return (newProcessor());
            } else {
                return (null);
            }
        }
    }

    private HttpProcessor newProcessor() {
        HttpProcessor processor = new HttpProcessor(this, curProcessors++);
        processor.start();
        created.add(processor);
        return (processor);
    }

    public void start() throws LifecycleException {
        if (started) {
            throw new LifecycleException(("httpConnector.alreadyStarted"));
        }
        threadName = "HttpConnector[" + port + "]";
        started = true;

        // Start our background thread
        threadStart();

        // Create the specified minimum number of processors
        while (curProcessors < minProcessors) {
            if (curProcessors >= maxProcessors) {
                break;
            }
            HttpProcessor processor = newProcessor();
            recycle(processor);
        }
    }

    @Override
    public void run() {
        while (!stopped) {
            Socket socket;
            try {
                socket = serverSocket.accept();
                if (connectionTimeout > 0) {
                    socket.setSoTimeout(connectionTimeout);
                }
                socket.setTcpNoDelay(tcpNoDelay);
            } catch (AccessControlException ace) {
                continue;
            } catch (IOException e) {
                try {
                    // If reopening fails, exit
                    synchronized (threadSync) {
                        if (!stopped) {
                            serverSocket.close();
                            serverSocket = open();
                        }
                    }
                } catch (IOException | NoSuchAlgorithmException | CertificateException
                        | UnrecoverableKeyException | KeyManagementException | KeyStoreException ioe) {
                    break;
                }
                continue;
            }

            // Hand this socket off to an appropriate processor
            HttpProcessor processor = createProcessor();
            if (processor == null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    // ignore;
                }
                continue;
            }
            processor.assign(socket);
        }

        synchronized (threadSync) {
            threadSync.notifyAll();
        }
    }

    void recycle(HttpProcessor processor) {
        processors.offer(processor);
    }

    private void threadStart() {
        thread = new Thread(this, threadName);
        thread.setDaemon(true);
        thread.start();
    }

    @Override
    public Container getContainer() {
        return container;
    }

    @Override
    public void setContainer(Container container) {
        this.container = container;
    }

    @Override
    public ServerSocketFactory getFactory() {
        if (this.factory == null) {
            synchronized (this) {
                this.factory = new DefaultServerSocketFactory();
            }
        }
        return (this.factory);
    }

    @Override
    public void setFactory(ServerSocketFactory factory) {

    }

    public void setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    public void setTcpNoDelay(boolean tcpNoDelay) {
        this.tcpNoDelay = tcpNoDelay;
    }

    @Override
    public boolean getEnableLookups() {
        return false;
    }

    @Override
    public void setEnableLookups(boolean enableLookups) {

    }

    @Override
    public String getInfo() {
        return null;
    }

    @Override
    public int getRedirectPort() {
        return 0;
    }

    @Override
    public void setRedirectPort(int redirectPort) {

    }

    @Override
    public String getScheme() {
        return scheme;
    }

    @Override
    public void setScheme(String scheme) {

    }

    @Override
    public boolean getSecure() {
        return false;
    }

    @Override
    public void setSecure(boolean secure) {

    }

    @Override
    public Service getService() {
        return null;
    }

    @Override
    public void setService(Service service) {

    }

    @Override
    public Request createRequest() {
        return null;
    }

    @Override
    public Response createResponse() {
        return null;
    }

}
